<?php
class KuaiQianPayService{
	public $merchant_id          = '';//商户号
	public $vpos_merchant_id     = '';//商家快钱VPOS商户编号
	public $soap_merchant_id     = '';//商家快钱SOAP商户编号10024638202
	public $product_id           = '';//快钱合作伙伴的帐户号，即商户编号
	public $terminal_id          = '';//终端号
	public $merchant_key         = '';//密钥
	public $cert_pass            = '';//密钥
	public $merchant_login_pass  = 'vpos123';//密钥
	public $notify_url           = '';//异步URL
	public $return_url           = '';//同步URL
	public $is_debug             = false;//true为调试模式，价格会设置成0.01，false为非调试模式，价格按正常来
	public $is_write_logs        = true;//开启日志
	public $api_url_prefix       = 'https://www.99bill.com/';
	public $api_url_vpos_prefix  = 'https://mas.99bill.com/';
	public $api_url_prefix_debug = 'https://sandbox.99bill.com/';
	
	//WEB支付接口
	public $api_web_pay             = 'gateway/recvMerchantInfoAction.htm';
	public $api_wap_pay             = 'mobilegateway/recvMerchantInfoAction.htm';
	//实时交易类型消息接口
	public $api_cnp_purchase        = 'cnp/purchase';
	//卡信息验证(需要发送动态码)
	public $api_cnp_ind_auth        = 'cnp/ind_auth';
	//卡信息验证-动态码验证
	public $api_cnp_ind_auth_verify = 'cnp/ind_auth_verify';
	//获取支付短信验证码
	public $api_cnp_get_dyn_num     = 'cnp/getDynNum';
	public $cer_path = '';
	public $pem_path = '';
	//错误代码
	public $error_code = array(
		'0000' => '支付成功',
		'1000' => '身份验证失败',
		'1001' => '信息填写不全',
		'1002' => '付款方帐号无效',
		'1003' => '加密方式不正确',
		'1004' => '收款方帐号无效',
		'1005' => '收款联系方式无效',
		'1006' => '交易描述不能大于 100 位 ',
		'1007' => '收款超过限额',
		'1008' => '费用大于付款金额',
		'1009' => '验签错误',
		'1010' => 'IP 地址不符',
		'1011' => '不匹配的接口类型',
		'1012' => '支付金额格式不正确',
		'1014' => '银行卡号不能为空',
		'1005' => 'email 或 mobile 格式不正确 ',
		'1016' => '不匹配的交易类型',
		'1017' => '货币代码错误',
		'1018' => '不能给自己付款',
		'1019' => '查询请求为空',
		'1020' => '查询类型无效',
		'1021' => '订单不存在',
		'1022' => '未指定查询起始时间',
		'1023' => '时间格式错误',
		'1024' => '无效数组',
		'1025' => '商家订单必须是 0-9 a-z A-Z 和_的字符组合',
		'1026' => '超过最大交易笔数限制',
		'1130' => '收款方银行账号有误！',
		'2001' => '服务器计费异常',
		'2002' => '服务处理异常',
		'2003' => '付款账户被冻结',
		'2004' => '其他异常',
		'3001' => '不是授权的会员',
		'3002' => '查询结果集为空',
		'3003' => '匹配收款人名称',
		'3004' => '不能给非快钱用户支付',
		'3005' => '通知异常',
		'3006' => '验签不能为空',
		'3010' => '付款超过限额',
		'3014' => '单笔付款超过限额',
		'3015' => '单笔收款超过限额',
		'4007' => '开户行不能为空',
		'4008' => '省份格式不正确',
		'4009' => '城市格式不正确',
		'4013' => '银行名称错误',
		'5201' => '订单不能被退款',
		'5203' => '不是订单的所有者',
		'5204' => '超出退款时限',
		'5211' => '订单号必须输入',
		'5212' => '商家订单号已经存在',
		'6001' => '余额不足',
		'6003' => '收款账户被冻结',
		'6006' => '交易引擎出错',
		'7000' => '批次号必须是 A-Z /0-9 和_的字符组合',
		'7001' => '批次号已经存在',
		'7002' => '付款会员号不能为空',
		'7003' => '付款账户不能为空',
		'7004' => '付款账户不存在',
		'7005' => '付款账户不是人民币账户',
		'7006' => '会员号和账户号不匹配',
		'7007' => '不匹配的字符集',
		'7008' => '主题信息验签不能为空',
		'7009' => '主题信息验签错误',
	);

	public function __construct($cer_path = '', $pem_path) {
		$this->init_config();
		if(!$this->is_debug){
			$this->cer_path = $cer_path ? $cer_path : $this->cer_path;
			$this->pem_path = $pem_path ? $pem_path : $this->pem_path;
		}
    }
    public function init_config($type = ''){
    	//日志保存目录
		$this->logs_path      = dirname(__FILE__).'/Logs/';
		$this->logs_path_file = $this->logs_path . 'debug_' . date('Y_m_d') . '.log';
		if(file_exists($this->logs_path) == false){
			mkdir($this->logs_path, 0700);
		}
		$this->notify_url = $this->notify_url ? $this->notify_url : U('KuaiqianPay/notify');
		
		if($type == 2){
			$this->merchant_id = $this->vpos_merchant_id;
		}
		//开启测试
		if($this->is_debug){
			$this->merchant_id         = '1001213884201';
			$this->product_id          = '';
			$this->terminal_id         = '00002012';//终端号
			$this->merchant_key        = 'J8A4CKE7H3HE8NUZ';
			$this->cert_pass           = 'vpos123';
			$this->merchant_login_pass = 'vpos123';
			$this->api_url_prefix      = $this->api_url_prefix_debug;
			if($type == 2){
				$this->merchant_id = '104110045112012';
			}
		}

		//证书文件路径
		$this->cer_path = $this->cer_path ? $this->cer_path : dirname(__FILE__) . '/cert/' . $this->merchant_id . '.cer';
		$this->pem_path = $this->pem_path ? $this->pem_path : dirname(__FILE__) . '/cert/' . $this->merchant_id . '.pem';
    }
    /**
     * 获取支付签名
     * @param  [type] $order_sn 订单号
     * @param  [type] $title    订单名称
     * @param  [type] $amount   订单金额
     */
    public function get_pay_sign_params($order_sn, $title, $amount, $pay_type = '00', $bank_id = ''){
    	if($this->is_debug){
    		$amount = 0.01;
    	}
    	//编码方式，1代表 UTF-8; 2 代表 GBK; 3代表 GB2312 默认为1,该参数必填。
		$params['inputCharset']     = '1';
		//前台回调
		$params['pageUrl']          = $this->return_url;
		//异步回调
		$params['bgUrl']            = $this->notify_url;
		//网关版本，固定值：v2.0,该参数必填。
		$params['version']          = 'v2.0';
		//语言种类，1代表中文显示，2代表英文显示。默认为1,该参数必填。
		$params['language']         = '1';
		//签名类型,该值为4，代表PKI加密方式,该参数必填。
		$params['signType']         = '4';
		//人民币网关账号，该账号为11位人民币网关商户编号+01,该参数必填。
		$params['merchantAcctId']   = $this->merchant_id;
		//支付人姓名,可以为空。
		$params['payerName']        = '';
		////支付人联系类型，1 代表电子邮件方式；2 代表手机联系方式。可以为空。
		$params['payerContactType'] = '';
		//支付人联系方式，与payerContactType设置对应，payerContactType为1，则填写邮箱地址；payerContactType为2，则填写手机号码。可以为空。
		$params['payerContact']     = '';
		//商户订单号，以下采用时间来定义订单号，商户可以根据自己订单号的定义规则来定义该值，不能为空。
		$params['orderId']          = $order_sn;
		//订单金额，金额以“分”为单位，商户测试以1分测试即可，切勿以大金额测试。该参数必填。
		$params['orderAmount']      = $amount * 100;
		//订单提交时间，格式：yyyyMMddHHmmss，如：20071117020101，不能为空。
		$params['orderTime']        = date("YmdHis");
		//商品名称，可以为空。
		$params['productName']      = $title;
		//商品数量，可以为空。
		$params['productNum']       = '';
		//商品代码，可以为空。
		$params['productId']        = '';
		//商品描述，可以为空。
		$params['productDesc']      = '';
		//扩展字段1，商户可以传递自己需要的参数，支付完快钱会原值返回，可以为空。
		$params['ext1']             = '';
		//扩展字段2，商户可以传递自己需要的参数，支付完快钱会原值返回，可以为空。
		$params['ext2']             = '';
		//支付方式，一般为00，代表所有的支付方式。如果是银行直连商户，该值为10，必填。
		$params['payType']          = $pay_type ? $pay_type : '00';
		//银行代码，如果payType为00，该值可以为空；如果payType为10，该值必须填写，具体请参考银行列表。
		$params['bankId']           = $pay_type == '10' ? ($bank_id ? $bank_id : 'CMB') : ($bank_id ? $bank_id : '');
		//同一订单禁止重复提交标志，实物购物车填1，虚拟产品用0。1代表只能提交一次，0代表在支付不成功情况下可以再提交。可为空。
		$params['redoFlag']         = '';
		//快钱合作伙伴的帐户号，即商户编号，可为空。
		$params['pid']              = $this->product_id;

		if($this->is_debug || $this->is_write_logs){
			\Think\Log::write('签名参数：' . print_r($params, true), \Think\Log::INFO, '', $this->logs_path_file);
		}

		$url_params = $this->get_url_params($params);

		if($this->is_debug || $this->is_write_logs){
			\Think\Log::write('签名URL串：' . print_r($url_params, true), \Think\Log::INFO, '', $this->logs_path_file);
		}

		$sign = $this->get_signature($url_params);

		if($this->is_debug || $this->is_write_logs){
			\Think\Log::write('签名：' . print_r($sign, true), \Think\Log::INFO, '', $this->logs_path_file);
		}

		$params['signMsg'] = $sign;

		return $params;
    }
    public function get_wap_sign_params($order_sn, $title, $amount, $order_time = '', $pay_type = '00', $bank_id = ''){
    	if($this->is_debug){
    		$amount = 0.01;
    	}
    	//编码方式，1代表 UTF-8; 2 代表 GBK; 3代表 GB2312 默认为1,该参数必填。
		$params['inputCharset']     = '1';
		//前台回调
		$params['pageUrl']          = $this->return_url;
		//异步回调
		$params['bgUrl']            = $this->notify_url;
		//网关版本，固定值：v2.0,该参数必填。
		$params['version']          = 'mobile1.0';
		//语言种类，1代表中文显示，2代表英文显示。默认为1,该参数必填。
		$params['language']         = '1';
		//签名类型,该值为4，代表PKI加密方式,该参数必填。
		$params['signType']         = '4';
		//人民币网关账号，该账号为11位人民币网关商户编号+01,该参数必填。
		$params['merchantAcctId']   = $this->merchant_id;
		//支付人姓名,可以为空。
		$params['payerName']        = '';
		////支付人联系类型，1 代表电子邮件方式；2 代表手机联系方式。可以为空。
		$params['payerContactType'] = '';
		//支付人联系方式，与payerContactType设置对应，payerContactType为1，则填写邮箱地址；payerContactType为2，则填写手机号码。可以为空。
		$params['payerContact']     = '';
		//商户订单号，以下采用时间来定义订单号，商户可以根据自己订单号的定义规则来定义该值，不能为空。
		$params['orderId']          = $order_sn;
		//订单金额，金额以“分”为单位，商户测试以1分测试即可，切勿以大金额测试。该参数必填。
		$params['orderAmount']      = $amount * 100;
		//订单提交时间，格式：yyyyMMddHHmmss，如：20071117020101，不能为空。
		$params['orderTime']        = $order_time ? $order_time : date("YmdHis");
		//商品名称，可以为空。
		$params['productName']      = $title;
		//商品数量，可以为空。
		$params['productNum']       = '';
		//商品代码，可以为空。
		$params['productId']        = '';
		//商品描述，可以为空。
		$params['productDesc']      = '';
		//扩展字段1，商户可以传递自己需要的参数，支付完快钱会原值返回，可以为空。
		$params['ext1']             = $this->ext1 ? $this->ext1 : '';
		//扩展字段2，商户可以传递自己需要的参数，支付完快钱会原值返回，可以为空。
		$params['ext2']             = $this->ext2 ? $this->ext2 : '';
		//支付方式，一般为00，代表所有的支付方式。如果是银行直连商户，该值为10，必填。
		$params['payType']          = $pay_type ? $pay_type : '00';
		//银行代码，如果payType为00，该值可以为空；如果payType为10，该值必须填写，具体请参考银行列表。
		$params['bankId']           = $pay_type == '10' ? ($bank_id ? $bank_id : 'CMB') : ($bank_id ? $bank_id : '');
		//同一订单禁止重复提交标志，实物购物车填1，虚拟产品用0。1代表只能提交一次，0代表在支付不成功情况下可以再提交。可为空。
		$params['redoFlag']         = '';
		//快钱合作伙伴的帐户号，即商户编号，可为空。
		$params['pid']              = $this->product_id;

		if($this->is_debug || $this->is_write_logs){
			\Think\Log::write('签名参数：' . print_r($params, true), \Think\Log::INFO, '', $this->logs_path_file);
		}

		$url_params = $this->get_url_params($params);

		if($this->is_debug || $this->is_write_logs){
			\Think\Log::write('签名URL串：' . print_r($url_params, true), \Think\Log::INFO, '', $this->logs_path_file);
		}
		$sign = $this->get_signature($url_params);

		if($this->is_debug || $this->is_write_logs){
			\Think\Log::write('签名：' . print_r($sign, true), \Think\Log::INFO, '', $this->logs_path_file);
		}

		$params['signMsg']    = $sign;

		return $params;  
    }
    public function get_url_params($params = ''){
    	foreach ($params as $key => $value) {
    		if($value != ""){
    			$url .= $key . '=' . $value . '&';
    		}
    	}
    	$url = substr($url, 0, -1);
    	return $url;
    }
    /**
     * WEB支付
     * @param  [type] $order_sn 订单号
     * @param  [type] $title    订单名称
     * @param  [type] $amount   订单金额
     * @param  string $pay_type 支付方式，00 代表显示快钱各支付方式列表(默讣开通 10、11、 12、13 四种支付方式);10 代表只显示银行卡支付方式;10-1 代表储蓄卡网银支付;10-2 代表信用卡网银 支付;11 代表只显示申话银行支付方式;11-1 代表储蓄卡申话支付;11-2 代表信用卡申话 支付;12 代表只显示快钱账户支付方式;13 代表只显示线下支付方式;14 代表显示企业网银支付;15 信用卡无卡支付;17 预付卡支付;19 手机诧音支付;19-1 代表储蓄卡手机诧音支付 ;19-2 代表信用卡 手机诧音支付;21 快捷支付;21-1 代表储蓄卡快捷;21-2 代表信用卡快捷
     * @param  string $bank_id  支付银行，如果pay_type为00，该值可以为空；如果pay_type为10，该值必须填写，具体请参考银行列表。
     */
    public function get_web_pay($order_sn, $title, $amount, $pay_type = '00', $bank_id = ''){
    	$this->init_config();
    	// $this->is_debug = true;
    	$params = $this->get_pay_sign_params($order_sn, $title, $amount, $pay_type, $bank_id);

    	$html  = '<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>快钱支付</title></head><body onLoad="document.pay.submit();">';
		$html .= '<form name="pay" action="' . $this->api_url_prefix . $this->api_web_pay . '" method="post">';
		foreach ($params as $key => $value) {
			$html .= '<input type="hidden" name="'. $key .'" value="' . $value . '" />';
		}
		$html .= '<input type="submit" value="跳转到快钱支付" /></form></body></html>';
		// header('Content-Type:text/html;charset=utf-8');
		// exit($html);
		return $html;
    }
    /**
     * WAP支付
     * @param  [type] $order_sn 订单号
     * @param  [type] $title    订单名称
     * @param  [type] $amount   订单金额
     * @param  string $pay_type 支付方式，00代表显示快钱各支付方式列表；15信用卡无卡支付;21 快捷支付;21-1 代表储蓄卡快捷；21-2 代表信用卡快捷
     * @param  string $bank_id  支付银行，如果pay_type为00，该值可以为空；如果pay_type为10，该值必须填写，具体请参考银行列表。
     */
    public function get_wap_pay_url($order_sn, $title, $amount, $order_time = '', $pay_type = '00', $bank_id = ''){
    	$this->init_config();
    	// $this->is_debug = true;
    	$params = $this->get_wap_sign_params($order_sn, $title, $amount, $order_time, $pay_type, $bank_id);

		foreach ($params as $key => $value) {
			$url_params .= $key . '=' . urlencode($value) . '&';
		}
		$url = $this->api_url_prefix . $this->api_wap_pay . '?' . $url_params;
		
		return $url;
    }
    public function get_callback_params(){
    	#//人民币网关账号，该账号为11位人民币网关商户编号+01,该值与提交时相同。
		$params['merchantAcctId'] = $_REQUEST['merchantAcctId'];
		//网关版本，固定值：v2.0,该值与提交时相同。
		$params['version']        = $_REQUEST['version'];
		//语言种类，1代表中文显示，2代表英文显示。默认为1,该值与提交时相同。
		$params['language']       = $_REQUEST['language'];
		//签名类型,该值为4，代表PKI加密方式,该值与提交时相同。
		$params['signType']       = $_REQUEST['signType'];
		//支付方式，一般为00，代表所有的支付方式。如果是银行直连商户，该值为10,该值与提交时相同。
		$params['payType']        = $_REQUEST['payType'];
		//银行代码，如果payType为00，该值为空；如果payType为10,该值与提交时相同。
		$params['bankId']         = $_REQUEST['bankId'];
		//商户订单号，,该值与提交时相同。
		$params['orderId']        = $_REQUEST['orderId'];
		//订单提交时间，格式：yyyyMMddHHmmss，如：20071117020101,该值与提交时相同。
		$params['orderTime']      = $_REQUEST['orderTime'];
		//订单金额，金额以“分”为单位，商户测试以1分测试即可，切勿以大金额测试,该值与支付时相同。
		$params['orderAmount']    = $_REQUEST['orderAmount'];
		// 快钱交易号，商户每一笔交易都会在快钱生成一个交易号。
		$params['dealId']         = $_REQUEST['dealId'];
		//银行交易号 ，快钱交易在银行支付时对应的交易号，如果不是通过银行卡支付，则为空
		$params['bankDealId']     = $_REQUEST['bankDealId'];
		//快钱交易时间，快钱对交易进行处理的时间,格式：yyyyMMddHHmmss，如：20071117020101
		$params['dealTime']       = $_REQUEST['dealTime'];
		//商户实际支付金额 以分为单位。比方10元，提交时金额应为1000。该金额代表商户快钱账户最终收到的金额。
		$params['payAmount']      = $_REQUEST['payAmount'];
		//费用，快钱收取商户的手续费，单位为分。
		$params['fee']            = $_REQUEST['fee'];
		//扩展字段1，该值与提交时相同
		$params['ext1']           = $_REQUEST['ext1'];
		//扩展字段2，该值与提交时相同。
		$params['ext2']           = $_REQUEST['ext2'];
		//处理结果， 10支付成功，11 支付失败，00订单申请成功，01 订单申请失败
		$params['payResult']      = $_REQUEST['payResult'];
		//错误代码 ，请参照《人民币网关接口文档》最后部分的详细解释。
		$params['errCode']        = $_REQUEST['errCode'];

		$trans_body = $this->get_url_params($params);

		$sign = base64_decode($_REQUEST['signMsg']);

		$fp   = fopen($this->cer_path, "r");
		$cert = fread($fp, 8192); 
		fclose($fp); 
		$pubkeyid = openssl_get_publickey($cert); 
		$ok = openssl_verify($trans_body, $sign, $pubkeyid);
		if(!$ok){
			return false;
		}
		$params['signMsg'] = $sign;
		return $params;
    }
    public function get_wap_callback_params(){
    	#//人民币网关账号，该账号为11位人民币网关商户编号+01,该值与提交时相同。
		$params['merchantAcctId'] = $_REQUEST['merchantAcctId'];
		//网关版本，固定值：v2.0,该值与提交时相同。
		$params['version']        = $_REQUEST['version'];
		//语言种类，1代表中文显示，2代表英文显示。默认为1,该值与提交时相同。
		$params['language']       = $_REQUEST['language'];
		//签名类型,该值为4，代表PKI加密方式,该值与提交时相同。
		$params['signType']       = $_REQUEST['signType'];
		//支付方式，一般为00，代表所有的支付方式。如果是银行直连商户，该值为10,该值与提交时相同。
		$params['payType']        = $_REQUEST['payType'];
		//银行代码，如果payType为00，该值为空；如果payType为10,该值与提交时相同。
		$params['bankId']         = $_REQUEST['bankId'];
		//商户订单号，,该值与提交时相同。
		$params['orderId']        = $_REQUEST['orderId'];
		//订单提交时间，格式：yyyyMMddHHmmss，如：20071117020101,该值与提交时相同。
		$params['orderTime']      = $_REQUEST['orderTime'];
		//订单金额，金额以“分”为单位，商户测试以1分测试即可，切勿以大金额测试,该值与支付时相同。
		$params['orderAmount']    = $_REQUEST['orderAmount'];
		$params['bindCard']       = $_REQUEST['bindCard'];
		$params['bindMobile']     = $_REQUEST['bindMobile'];
		$params['dealId']         = $_REQUEST['dealId'];
		$params['bankDealId']     = $_REQUEST['bankDealId'];
		$params['dealTime']       = $_REQUEST['dealTime'];
		$params['payAmount']      = $_REQUEST['payAmount'];
		$params['fee']            = $_REQUEST['fee'];
		$params['ext1']           = $_REQUEST['ext1'];
		$params['ext2']           = $_REQUEST['ext2'];
		$params['payResult']      = $_REQUEST['payResult'];
		$params['errCode']        = $_REQUEST['errCode'];

		$trans_body = $this->get_url_params($params);

		$sign     = base64_decode($_REQUEST['signMsg']);
		$cert     = file_get_contents($this->cer_path);
		$pubkeyid = openssl_get_publickey($cert); 
		$ok = openssl_verify($trans_body, $sign, $pubkeyid);

		if(!$ok){
			return false;
		}
		$params['signMsg'] = $sign;
		return $params;
    }
    /**
     * 发送短信验证码（卡信息验证）
     * @param  string $user_id      用户ID
     * @param  string $card_no      卡号
     * @param  string $realname     姓名
     * @param  string $cardid       身份证
     * @param  string $mobile       手机号
     * @param  string $expired_date 信用卡有效期mmyy
     * @param  string $cvv2         卡校验码
     */
    public function send_smscode($user_id = '', $card_no = '', $realname = '', $cardid = '', $mobile = '', $expired_date = '', $cvv2 = ''){
		$this->init_config(2);
		$params['merchantId']        = $this->merchant_id;//商户号
		$params['terminalId']        = $this->terminal_id;//终端号
		$params['customerId']        = $user_id;//客户号
		$params['externalRefNumber'] = $user_id;//外部跟踪编号
		$params['pan']               = $card_no;//卡号
		$params['cardHolderName']    = $realname;//持卡人户名
		$params['idType']            = 0;//证件类型：0身份证类型，1护照类型，2军官证，3士兵证，4港澳台通行证，5临时身份证，6户口本，7其他类型证件
		$params['cardHolderId']      = $cardid;//证件号码
		$params['phoneNO']           = $mobile;//手机号码
		$params['expiredDate']       = $expired_date;//有效期
		$params['cvv2']              = $cvv2;//卡校验码

		//获取XML格式数据
		$xml = $this->get_send_xml($params, 'indAuthContent');
		//提交数据并返回结果
		$data = $this->post_xml_data($xml, $this->api_cnp_ind_auth, 'indAuthContent');

		if($this->is_debug || $this->is_write_logs){
			\Think\Log::write('发送短信验证码request：' . print_r($xml, true), \Think\Log::INFO, '', $this->logs_path_file);
			\Think\Log::write('发送短信验证码response：' . print_r($data, true), \Think\Log::INFO, '', $this->logs_path_file);
		}
		if($data['responseCode'] == '00'){
			$info = $this->success('获取成功！', array('token' => $data['token']));
		}else{
			$info = $this->error($data['responseTextMessage']);
		}
		return $info;
    }
    /**
     * 验证短信验证码
     * @param  string $user_id      用户ID
     * @param  string $card_no      卡号
     * @param  string $mobile       手机号
     * @param  string $smscode      短信验证码
     * @param  string $token        回调token
     */
    public function verify_smscode($user_id = '', $card_no = '', $mobile = '', $smscode = '', $token = ''){
		$this->init_config(2);
		$params['merchantId']        = $this->merchant_id;//商户号
		$params['customerId']        = $user_id;//客户号
		$params['externalRefNumber'] = $user_id;//外部跟踪编号
		$params['pan']               = $card_no;//卡号
		$params['phoneNO']           = $mobile;//手机号码
		$params['validCode']         = $smscode;//短信验证码
		$params['token']             = $token;//回调token

		//获取XML格式数据
		$xml = $this->get_send_xml($params, 'indAuthDynVerifyContent');
		//提交数据并返回结果
		$data = $this->post_xml_data($xml, $this->api_cnp_ind_auth_verify, 'indAuthDynVerifyContent');

		if($this->is_debug || $this->is_write_logs){
			\Think\Log::write('绑定银行卡request：' . print_r($xml, true), \Think\Log::INFO, '', $this->logs_path_file);
			\Think\Log::write('绑定银行卡response：' . print_r($data, true), \Think\Log::INFO, '', $this->logs_path_file);
		}
		if($data['responseCode'] == '00'){
			$info = $this->success('绑定成功', array('storable_card_no' => $data['storablePan']));
		}else{
			$info = $this->error($data['responseTextMessage']);
		}
		return $info;
    }
    /**
     * 发送支付短信验证码
     * @param  string $order_sn         订单号
     * @param  string $user_id          用户ID
     * @param  string $bank_code        银行代码
     * @param  string $storable_card_no 缩略卡号
     * @param  string $amount           支付金额
     */
    public function send_pay_smscode($order_sn = '', $user_id = '', $bank_code = '', $storable_card_no = '', $amount = ''){
		$this->init_config(2);
		$params['merchantId']        = $this->merchant_id;//商户号
		$params['customerId']        = $user_id;//客户号
		$params['externalRefNumber'] = $order_sn;//外部跟踪编号
		$params['storablePan']       = $storable_card_no;//缩略卡号
		$params['bankId']            = $bank_code;//银行代码
		$params['amount']            = $amount;//支付金额

		//获取XML格式数据
		$xml = $this->get_send_xml($params, 'GetDynNumContent');
		//提交数据并返回结果
		$data = $this->post_xml_data($xml, $this->api_cnp_get_dyn_num, 'GetDynNumContent');

		if($this->is_debug || $this->is_write_logs){
			\Think\Log::write('发送支付短信验证码request：' . print_r($xml, true), \Think\Log::INFO, '', $this->logs_path_file);
			\Think\Log::write('发送支付短信验证码response：' . print_r($data, true), \Think\Log::INFO, '', $this->logs_path_file);
		}
		if($data['responseCode'] == '00'){
			$info = $this->success('获取成功！', array('token' => $data['token']));
		}else{
			$info = $this->error($data['responseTextMessage']);
		}
		return $info;
    }
    /**
     * 发送支付短信验证码(首付支付)
     * @param  string $order_sn     订单号
     * @param  string $user_id      用户ID
     * @param  string $card_no      银行卡号
     * @param  string $realname     姓名
     * @param  string $cardid       身份证
     * @param  string $mobile       手机号
     * @param  string $expired_date 有效期mmyy
     * @param  string $cvv2         卡校验码 CVV2
     * @param  string $bank_code    银行代码
     * @param  string $amount       支付金额
     */
    public function first_send_pay_smscode($order_sn = '', $user_id = '', $card_no = '', $realname = '', $cardid = '', $mobile = '', $expired_date = '', $cvv2 = '', $bank_code = '', $amount = ''){
		$this->init_config(2);
		$params['merchantId']        = $this->merchant_id;//商户号
		$params['customerId']        = $user_id;//客户号
		$params['externalRefNumber'] = $order_sn;//外部跟踪编号
		
		$params['cardHolderName']    = $realname;//持卡人户名
		$params['idType']            = 0;//证件类型：0身份证类型，1护照类型，2军官证，3士兵证，4港澳台通行证，5临时身份证，6户口本，7其他类型证件
		$params['cardHolderId']      = $cardid;//证件号码
		$params['pan']               = $card_no;//卡号
		$params['phoneNO']           = $mobile;//手机号码
		$params['expiredDate']       = $expired_date;//有效期
		$params['cvv2']              = $cvv2;//卡校验码
		
		$params['bankId']            = $bank_code;//银行代码
		$params['amount']            = $amount;//支付金额

		//获取XML格式数据
		$xml = $this->get_send_xml($params, 'GetDynNumContent');
		//提交数据并返回结果
		$data = $this->post_xml_data($xml, $this->api_cnp_get_dyn_num, 'GetDynNumContent');

		if($this->is_debug || $this->is_write_logs){
			\Think\Log::write('发送首次支付短信验证码request：' . print_r($xml, true), \Think\Log::INFO, '', $this->logs_path_file);
			\Think\Log::write('发送首次支付短信验证码response：' . print_r($data, true), \Think\Log::INFO, '', $this->logs_path_file);
		}
		if($data['responseCode'] == '00'){
			$info = $this->success('获取成功！', $data);
		}else{
			$info = $this->error($data['responseTextMessage']);
		}
		return $info;
    }
    /**
     * 快捷支付确认
     * @param  string $order_sn         订单号
     * @param  string $user_id          用户ID
     * @param  string $mobile           手机号
     * @param  string $bank_code        银行代码
     * @param  string $card_no 			卡号
     * @param  string $storable_card_no 缩略卡号
     * @param  string $amount           支付金额
     * @param  string $smscode          短信验证码
     * @param  string $token            回调token
     */
    public function quick_pay_confirm($order_sn = '', $user_id = '', $mobile = '', $bank_code = '', $card_no = '', $storable_card_no = '', $amount = '', $smscode = '', $token = ''){
		$this->init_config(2);
		$params['txnType']           = 'PUR';//交易类型编码
		$params['interactiveStatus'] = 'TR1';//消息状态
		$params['merchantId']        = $this->merchant_id;//商户号
		$params['terminalId']        = $this->terminal_id;//终端号
		$params['customerId']        = $user_id;//客户号
		$params['amount']            = $amount;//支付金额，以分为单位
		$params['cardNo']            = $card_no;//卡号
		$params['storableCardNo']    = $storable_card_no;//缩略卡号
		$params['tr3Url']            = $this->notify_url;//回调地址
		$params['entryTime']         = date('YmdHis');//商户端交易时间
		$params['externalRefNumber'] = $order_sn;//订单号
		$params['spFlag']            = 'QuickPay';//订单号

		//扩展字段处理
		$pay_batch = $this->pay_batch ? $this->pay_batch : 2;//快捷支付批次 1:首次支付 2:再次支付
		$save_pci_flag = $pay_batch == 1 ? 1 : 0;//1保存
		$ext_xml  = "<extDate><key>phone</key><value>" . $mobile . "</value></extDate>";
		$ext_xml .= "<extDate><key>validCode</key><value>" . $smscode . "</value></extDate>";
		$ext_xml .= "<extDate><key>savePciFlag</key><value>" . $save_pci_flag . "</value></extDate>";
		$ext_xml .= "<extDate><key>token</key><value>" . $token . "</value></extDate>";
		$ext_xml .= "<extDate><key>payBatch</key><value>" . $pay_batch . "</value></extDate>";
		$params['extMap'] = $ext_xml;

		//获取XML格式数据
		$xml = $this->get_send_xml($params, 'TxnMsgContent');
		//提交数据并返回结果
		$data = $this->post_xml_data($xml, $this->api_cnp_purchase, 'TxnMsgContent');

		if($this->is_debug || $this->is_write_logs){
			\Think\Log::write('快捷支付确认request：' . print_r($xml, true), \Think\Log::INFO, '', $this->logs_path_file);
			\Think\Log::write('快捷支付确认response：' . print_r($data, true), \Think\Log::INFO, '', $this->logs_path_file);
		}
		if($data['responseCode'] == '00'){
			$info = $this->success('支付成功！', $data);
		}else{
			$info = $this->error($data['responseTextMessage']);
		}
		return $info;
    }
    /**
     * 快捷支付确认（首次）
     * @param  string $order_sn         订单号
     * @param  string $user_id          用户ID
     * @param  string $mobile           手机号
     * @param  string $bank_code        银行代码
     * @param  string $card_no 			卡号
     * @param  string $storable_card_no 缩略卡号
     * @param  string $amount           支付金额
     * @param  string $smscode          短信验证码
     * @param  string $token            回调token
     */
    public function first_quick_pay_confirm($order_sn = '', $user_id = '', $realname = '', $cardid = '', $mobile = '', $bank_code = '', $card_no = '', $storable_card_no = '', $expired_date = '', $cvv2 = '', $amount = '', $smscode = '', $token = ''){
		$this->init_config(2);
		$params['txnType']           = 'PUR';//交易类型编码
		$params['interactiveStatus'] = 'TR1';//消息状态
		$params['merchantId']        = $this->merchant_id;//商户号
		$params['terminalId']        = $this->terminal_id;//终端号
		$params['customerId']        = $user_id;//客户号
		$params['amount']            = $amount;//支付金额，以分为单位
		$params['tr3Url']            = $this->notify_url;//回调地址
		$params['entryTime']         = date('YmdHis');//商户端交易时间
		$params['externalRefNumber'] = $order_sn;//订单号
		$params['spFlag']            = 'QuickPay';//

		$params['cardNo']            = $card_no;//卡号
		$params['storableCardNo']    = $storable_card_no;//缩略卡号
		$params['expiredDate']       = $expired_date;//有效期
		$params['cvv2']              = $cvv2;//卡校验码

		$params['cardHolderName']    = $realname;//持卡人户名
		$params['idType']            = 0;//证件类型：0身份证类型，1护照类型，2军官证，3士兵证，4港澳台通行证，5临时身份证，6户口本，7其他类型证件
		$params['cardHolderId']      = $cardid;//证件号码

		//扩展字段处理
		$pay_batch = $this->pay_batch ? $this->pay_batch : 2;//快捷支付批次 1:首次支付 2:再次支付
		$save_pci_flag = $pay_batch == 1 ? 1 : 0;//1保存
		$ext_xml  = "<extDate><key>phone</key><value>" . $mobile . "</value></extDate>";
		$ext_xml .= "<extDate><key>validCode</key><value>" . $smscode . "</value></extDate>";
		$ext_xml .= "<extDate><key>savePciFlag</key><value>" . $save_pci_flag . "</value></extDate>";
		$ext_xml .= "<extDate><key>token</key><value>" . $token . "</value></extDate>";
		$ext_xml .= "<extDate><key>payBatch</key><value>" . $pay_batch . "</value></extDate>";
		$params['extMap'] = $ext_xml;

		//获取XML格式数据
		$xml = $this->get_send_xml($params, 'TxnMsgContent');
		//提交数据并返回结果
		$data = $this->post_xml_data($xml, $this->api_cnp_purchase, 'TxnMsgContent');

		if($this->is_debug || $this->is_write_logs){
			\Think\Log::write('快捷支付确认request：' . print_r($xml, true), \Think\Log::INFO, '', $this->logs_path_file);
			\Think\Log::write('快捷支付确认response：' . print_r($data, true), \Think\Log::INFO, '', $this->logs_path_file);
		}
		if($data['responseCode'] == '00'){
			$info = $this->success('支付成功！', $data);
		}else{
			$info = $this->error($data['responseTextMessage']);
		}
		return $info;
    }
    /**
     * 签名
     * @param  string $content 需要签名内容
     */
    public function get_signature($content = ''){
    	// $pem_path = dirname(__FILE__) . '/cert/' . $this->merchant_id . '.pem';
    	// var_dump(file_get_contents($pem_path));
    	// $fp = fopen($pem_path, "r");
    	// $priv_key = fread($fp, $this->merchant_key);
		// fclose($fp);
		$priv_key = file_get_contents($this->pem_path);
		$pkeyid   = openssl_get_privatekey($priv_key);

		// compute signature
		openssl_sign($content, $sign, $pkeyid, OPENSSL_ALGO_SHA1);

		// free the key from memory
		openssl_free_key($pkeyid);

		$sign = base64_encode($sign);
		return $sign;
    }
    /**
     * 获取发送XML
     * @param  string $params 参数
     */
    public function get_send_xml($params='', $head_key = 'TxnMsgContent'){
    	$xml  = '<?xml version="1.0" encoding="UTF-8"?>';
    	$xml .= '<MasMessage xmlns="http://www.99bill.com/mas_cnp_merchant_interface">';
    	$xml .= '<version>1.0</version>';
    	$xml .= '<' . $head_key . '>';
    	foreach ($params as $key => $value) {
    		if($value != ''){
    			$xml .= '<' . $key . '>' . trim($value) . '</' . $key . '>';
    		}
    	}
    	$xml .= '</' . $head_key . '>';
    	$xml .= '</MasMessage>';

    	return $xml;
    }
    /**
     * 提交XML数据到快钱
     * @param  [type] $xml     XML格式数据
     * @param  string $api_action 接口地址
     */
    public function post_xml_data($xml, $api_action = '', $get_data_node = ''){

		$curl = curl_init();
		$login_info = array(
			"Authorization: Basic " . base64_encode($this->merchant_id . ":" . $this->merchant_login_pass)
		);
		
		if($this->is_debug){
			$this->api_url_vpos_prefix = $this->api_url_prefix_debug;
			$port = ':9445';
		}
		$this->api_url_prefix = substr($this->api_url_vpos_prefix, 0, -1);
		$api_url = $this->api_url_prefix . $port . '/' . $api_action;
    	
    	// var_dump($api_url);
    	// var_dump($this->pem_path);
    	// var_dump($this->merchant_id);
    	// var_dump($this->merchant_login_pass);
    	// var_dump($this->cert_pass);

		curl_setopt($curl, CURLOPT_URL, $api_url);
		curl_setopt($curl, CURLOPT_USERPWD, $this->merchant_login_pass);
		curl_setopt($curl, CURLOPT_POST, 1);
		curl_setopt($curl, CURLOPT_SSLVERSION, 4);
		curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
		curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
		curl_setopt($curl, CURLOPT_HTTPHEADER, $login_info);
		curl_setopt($curl, CURLOPT_SSLCERT, $this->pem_path);
		curl_setopt($curl, CURLOPT_SSLCERTPASSWD, $this->cert_pass);
		curl_setopt($curl, CURLOPT_SSLKEYTYPE, 'PEM');
		curl_setopt($curl, CURLOPT_POSTFIELDS, $xml);
		curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);

		$rexml = curl_exec($curl);
		if(curl_errno($curl)){
			$error_info = 'Curl error: ' . curl_error($curl) ."\r\n";
			$error_info .= 'API URL: ' . $api_url ."\r\n";
			$error_info .= 'PEM Path: ' . $this->pem_path ."\r\n";
			$error_info .= 'merchant_id: ' . $this->merchant_id ."\r\n";
			$error_info .= 'cert_pass: ' . $this->cert_pass ."\r\n";
			$error_info .= 'merchant_login_pass: ' . $this->merchant_login_pass ."\r\n";
			\Think\Log::write($error_info, \Think\Log::INFO, '', $this->logs_path_file);
		}
		$data  = xml_to_array($rexml);
		if($this->is_debug || $this->is_write_logs){
			\Think\Log::write('API URL：' . $api_url, \Think\Log::INFO, '', $this->logs_path_file);
			\Think\Log::write('response：' . print_r($data, true), \Think\Log::INFO, '', $this->logs_path_file);
		}
		//返回数据节点
		if($get_data_node){
			return $data[$get_data_node];
		}

		return $data;

    }

    /**
     * 付款到银行卡
     * @param  [type] $order_sn       订单号
     * @param  [type] $realname       收款人姓名
     * @param  [type] $city_name      城市名称
     * @param  [type] $bank_name      银行名称
     * @param  [type] $open_bank_name 开户行
     * @param  [type] $bank_card_no   银行卡号
     * @param  [type] $amount         交易金额
     * @param  [type] $description    交易备注
     */
    public function pay_to_bank($order_sn, $realname, $city_name, $bank_name, $open_bank_name, $bank_card_no, $amount, $description = ''){
		//去掉整数带.00的，避免签名错误
		$amount = str_replace('.00', '', $amount);

		$params['province_city']  = $city_name;//城市,主要只需要城市名,不需要省份名。也不要带“市” “自治区(县)”等。
		$params['bankName']       = $bank_name;//银行名称
		$params['kaihuhang']      = $open_bank_name;//开户行
		$params['creditName']     = $realname;//收款人姓名
		$params['bankCardNumber'] = $bank_card_no;//银行卡号
		$params['amount']         = $amount;//交易金额,小数为两位以内。单位为人民币元
		$params['description']    = $description;//交易备注
		$params['orderId']        = $order_sn;//字符串字母、数字、及-、_ 的组合。并且必须以字母或数 字开头。
		
		$mac_str = $bank_card_no . $amount . $order_sn . $this->merchant_key;
		
		$mac_md5 = strtoupper(md5($mac_str));
		$params['mac'] = $mac_md5;

		\Think\Log::write('参数mac_str:' . $mac_str, \Think\Log::INFO, '', $this->logs_path_file);
		\Think\Log::write('参数mac_md5:' . $mac_md5, \Think\Log::INFO, '', $this->logs_path_file);

		$soap_url = $this->api_url_prefix . 'webapp/services/BatchPayWS?wsdl';
		$data = $this->soap($soap_url, 'bankPay', array($params));
		if($data['resultFlag']){
			$info = $this->success('付款成功！', $data);
		}else{
			$info = $this->error($data['msg']);
		}
		return $info;
    }
    /**
     * 查询付款到银行状态
     * @param  string  $order_sn [description]
     * @param  integer $type     [description]
     * @return [type]            [description]
     */
    public function query_pay_bank_status($trade_no = ''){
		$params['dealId']    = $trade_no;//0 代表按商户订单号查询;1 代表按交易时间范围查询,也可填写正常的快钱交易号,按照快钱交易号查询
		$params['queryType'] = 'bankPay';//simplePay 代表付款到快钱账户 bankPay 代表付款到银行账户
		// $params['orderId']   = $order_sn;//商户提交时的订单号

		$soap_url = $this->api_url_prefix . 'webapp/services/BatchPayWS?wsdl';
		$data = $this->soap($soap_url, 'queryDeal', $params);

		if($data['resultFlag'] && $data['dealStatus'] == '111'){
			$info = $this->success('已到账！', $data);
		}else{
			$dealStatus = array(
				'111' => '成功',
				'101' => '银行受理中',
				'112' => '打款失败',
				'114' => '已退款',
			);
			$data['msg'] = $data['msg'] ? $data['msg'] : ($data['dealStatus'] ? $dealStatus[$data['dealStatus']] : $data['msg']);
			$info = $this->error($data['msg'], $data);
		}
		return $info;
    }
    public function soap($soap_url, $method = 'bankPay', $params = array()){
    	$this->merchant_id = $this->soap_merchant_id;
    	if($this->is_debug){
    		$this->merchant_id = '10012138842';
    	}

    	\Think\Log::write('请求params:' . print_r($params, true), \Think\Log::INFO, '', $this->logs_path_file);
		\Think\Log::write('merchant_key:' . $this->merchant_key, \Think\Log::INFO, '', $this->logs_path_file);
		\Think\Log::write('merchant_id:' . $this->merchant_id, \Think\Log::INFO, '', $this->logs_path_file);
		\Think\Log::write('SOAP URL:' . $soap_url, \Think\Log::INFO, '', $this->logs_path_file);
		\Think\Log::write('SOAP Method:' . $method, \Think\Log::INFO, '', $this->logs_path_file);

		$clientObj = new SoapClient($soap_url);
    	try {
    		$server_ip = $_SERVER['SERVER_ADDR'];
			$result = $clientObj->__soapCall($method, array($params, $this->merchant_id, $server_ip));
			$result =  json_decode(json_encode($result), true);
			$data   = $result[0];
			$data['msg'] = $this->error_code[$data['failureCause']];

		    \Think\Log::write('SOAP Result:' . print_r($result, true), \Think\Log::INFO, '', $this->logs_path_file);

		} catch (SOAPFault $e) {
		    \Think\Log::write('SOAP Exception:' . $e, \Think\Log::INFO, '', $this->logs_path_file);
		}

		return $data ? $data : '';
    }
    public function success($msg, $data){
		$info['data']   = $data ? $data : null;
		$info['info']   = $msg ? $msg : '成功';
		$info['status'] = 200;
		return $info;
    }
    public function error($msg, $data){
		$info['data']   = $data ? $data : null;
		$info['info']   = $msg ? $msg : '失败';
		$info['status'] = 400;
		return $info;
    }
}


?>